package semant.second_pass;

import util.List;
import semant.Env;
import symbol.Symbol;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.VarInfo;
import syntaxtree.Exp;
import syntaxtree.And;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.Call;
import syntaxtree.Equal;
import syntaxtree.False;
import syntaxtree.Identifier;
import syntaxtree.IdentifierExp;
import syntaxtree.IntegerLiteral;
import syntaxtree.LessThan;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.Type;
import syntaxtree.BooleanType;
import syntaxtree.IntegerType;
import syntaxtree.IntArrayType;
import syntaxtree.IdentifierType;
import syntaxtree.TypeVisitorAdapter;

final class ExpHandler extends TypeVisitorAdapter {

    private Env env;
    private ClassInfo cinfo;
    private MethodInfo minfo;

    private ExpHandler(Env e, ClassInfo c, MethodInfo m){
	super();
	env = e;
	cinfo = c;
	minfo = m;
    }

    static Type secondPass(Env e, ClassInfo c, MethodInfo m, Exp exp){
	ExpHandler h = new ExpHandler(e, c, m);
	exp.accept(h);
	return exp.type;
    }

    static VarInfo getVariable(ClassInfo c, MethodInfo m, Symbol name){
	// procura a variavel inicialmente nos formals do metodo
	VarInfo v = m.formalsTable.get(name);
	if(v == null){
	    // nao encontrou, procura em locals
	    v = m.localsTable.get(name);
	    if(v == null){
		// nao encontrou, ultima tentativa nos atributos da classe
		v = c.attributes.get(name);
	    }
	}
	return v;
    }

    public Type visit(And node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	if(!(lhs instanceof BooleanType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '&&'",
				       "Esperado: boolean",
				       "Encontrado: " + lhs});

	if(!(rhs instanceof BooleanType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '&&'",
				       "Esperado: boolean",
				       "Encontrado: " + rhs});

	return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(ArrayLength node){
	Type exp = ExpHandler.secondPass(env, cinfo, minfo, node.array);
	if(!(exp instanceof IntArrayType))
	    env.err.Error(node.array,
			  new Object[]{"Tipo invalido de objeto para chamada de 'length'",
				       "Esperado: int []",
				       "Encontrado: " + exp});

	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(ArrayLookup node){
	Type array = ExpHandler.secondPass(env, cinfo, minfo, node.array);
	Type index = ExpHandler.secondPass(env, cinfo, minfo, node.index);

	if(!(array instanceof IntArrayType))
	    env.err.Error(node.array,
			  new Object[]{"Tipo invalido de objeto para consulta de elemento de vetor",
				       "Esperado: int []",
				       "Encontrado: " + array});

	if(!(index instanceof IntegerType))
	    env.err.Error(node.index,
			  new Object[]{"Tipo invalido de objeto para indice de vetor",
				       "Esperado: int",
				       "Encontrado: " + index});

	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(Call node){
	Type objType = ExpHandler.secondPass(env, cinfo, minfo, node.object);
	if(!(objType instanceof IdentifierType)){
	    env.err.Error(node,
			  new Object[]{"Tipo invalido de objeto para chamada de metodo",
				       "Esperado: <class>",
				       "Encontrado: " + objType});
	    return node.type = new IntegerType(node.line, node.row);
	}

	Symbol cname = Symbol.symbol(((IdentifierType)objType).name);
	ClassInfo c = env.classes.get(cname);
	if(c != null){
	    Symbol mname = Symbol.symbol(node.method.s);
	    MethodInfo m = c.methods.get(mname);

	    if(m == null){
		env.err.Error(node, new Object[]{"Metodo '" + mname + "' nao definido para classe '" + cname + "'"});
		return node.type = new IntegerType(node.line, node.row);
	    }

	    // verifica parametros na chamada do metodo
	    List<VarInfo> vinfo = m.formals;
	    List<Exp> param = node.actuals;
	    int i = 1;
	    while(vinfo != null && param != null){
		Type tformal = vinfo.head.type;
		Type tparam = ExpHandler.secondPass(env, cinfo, minfo, param.head);
		if(!TypeHandler.compatible(env, tformal, tparam)){
		    env.err.Error(tparam,
				  new Object[]{"Tipo incompativel do argumento #" + i + " para o metodo '" + cname + "." + mname + "'",
					       "Esperado: " + tformal,
					       "Encontrado: " + tparam});
		}
		vinfo = vinfo.tail;
		param = param.tail;
		i++;
	    }

	    // verifica numero de parametros passados
	    if(vinfo != null || param != null)
		env.err.Error(node,
			      new Object[]{"Numero de parametros invalido para o metodo '" + cname + "." + mname + "'"});

	    return node.type = m.type;

	}else{
	    // nao precisamos reportar o erro de classe inexistente
	    // o erro ja foi detectado anteriormente
	    return node.type = new IntegerType(node.line, node.row);
	}
    }

    public Type visit(Equal node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	// lado esquerdo deve ser int ou boolean
	if(!(lhs instanceof BooleanType) && !(lhs instanceof IntegerType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '=='",
				       "Esperado: boolean ou int",
				       "Encontrado: " + lhs});

	// lado direito deve ser int ou boolean
	if(!(rhs instanceof BooleanType) && !(rhs instanceof IntegerType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '=='",
				       "Esperado: boolean ou int",
				       "Encontrado: " + rhs});

	// os dois lados devem ser do mesmo tipo
	if((lhs instanceof IntegerType && rhs instanceof BooleanType) ||
	   (rhs instanceof IntegerType && lhs instanceof BooleanType))
	    env.err.Error(node,
			  new Object[]{"Tipos incomparaveis para o operador '=='",
				       "Lado esquerdo: " + lhs,
				       "Lado direito: " + rhs});

	return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(False node){
	return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(IdentifierExp node){
	Symbol name = Symbol.symbol(node.name.s);
	VarInfo v = ExpHandler.getVariable(cinfo, minfo, name);
	if(v == null){
	    env.err.Error(node, new Object[]{"Variavel '" + name + "' nao declarada no metodo atual"});
	    return new IntegerType(node.line, node.row);
	}

	return node.type = v.type;
    }

    public Type visit(IntegerLiteral node){
	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(LessThan node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	if(!(lhs instanceof IntegerType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '<'",
				       "Esperado: int",
				       "Encontrado: " + lhs});

	if(!(rhs instanceof IntegerType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '<'",
				       "Esperado: int",
				       "Encontrado: " + rhs});

	return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(Minus node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	if(!(lhs instanceof IntegerType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '-'",
				       "Esperado: int",
				       "Encontrado: " + lhs});

	if(!(rhs instanceof IntegerType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '-'",
				       "Esperado: int",
				       "Encontrado: " + rhs});

	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(NewArray node){
	Type exp = ExpHandler.secondPass(env, cinfo, minfo, node.size);
	if(!(exp instanceof IntegerType))
	    env.err.Error(node.size,
			  new Object[]{"Tipo invalido para tamanho de vetor",
				       "Esperado: int",
				       "Encontrado: " + exp});

	return node.type = new IntArrayType(node.line, node.row);
    }

    public Type visit(NewObject node){
	Type t = new IdentifierType(node.line, node.row, node.className.s);
	TypeHandler.secondPass(env, t);
	return node.type = t;
    }

    public Type visit(Not node){
	Type exp = ExpHandler.secondPass(env, cinfo, minfo, node.exp);
	if(!(exp instanceof BooleanType))
	    env.err.Error(node.exp,
			  new Object[]{"Tipo invalido para operador '!'",
				       "Esperado: boolean",
				       "Encontrado: " + exp});

	return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(Plus node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	if(!(lhs instanceof IntegerType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '+'",
				       "Esperado: int",
				       "Encontrado: " + lhs});

	if(!(rhs instanceof IntegerType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '+'",
				       "Esperado: int",
				       "Encontrado: " + rhs});

	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(This node){
	// this em contexto estatico
	if(minfo.name.toString().equals("main"))
	    env.err.Error(node, new Object[]{"Uso de 'this' em contexto estatico"});
	return node.type = new IdentifierType(node.line, node.row, cinfo.name.toString());
    }

    public Type visit(Times node){
	Type lhs = ExpHandler.secondPass(env, cinfo, minfo, node.lhs);
	Type rhs = ExpHandler.secondPass(env, cinfo, minfo, node.rhs);

	if(!(lhs instanceof IntegerType))
	    env.err.Error(node.lhs,
			  new Object[]{"Tipo invalido para lado esquerdo do operador '*'",
				       "Esperado: int",
				       "Encontrado: " + lhs});

	if(!(rhs instanceof IntegerType))
	    env.err.Error(node.rhs,
			  new Object[]{"Tipo invalido para lado direito do operador '*'",
				       "Esperado: int",
				       "Encontrado: " + rhs});

	return node.type = new IntegerType(node.line, node.row);
    }

    public Type visit(True node){
	return node.type = new BooleanType(node.line, node.row);
    }

}
